package demo12


import "fmt"

// --------------------------------场景1-----------------------------------
// 官方案例
// 装饰器模式：在不修改原有功能的基础上增加新的功能
// Component | ConcreteComponent | MulDecorator | WarpMulDecorator | AddDecorator | WarpAddDecorator
// 开始

// 定义接口，接口内分析方法和返回值类型(属性)
type Component interface{
	Calc() int
}

type ConcreteComponent struct{}
func (c *ConcreteComponent)Calc() int{
	return 0
}

// 上面定义base 功能，下面是拓展，有点类似模板设计模式(该模式使用继承来实现的)，继承的实现和装饰器的实现差不多

type MulDecorator struct{
	// 这里嵌套的是接口 | 模板方法是对应的实例结构体，
	Component
	num int
}
// 构建函数，初始化，同时传入要装饰的对象
func WarpMulDecorator(component Component,num int) Component{
	return &MulDecorator{
		Component:component,
		num:num,
	}
}

func (m *MulDecorator)Calc() int{
	return m.Component.Calc() * m.num
}

type AddDecorator struct{
	Component
	num int
}
func WarpAddDecorator(component Component,num int) Component{
	return &AddDecorator{
		Component:component,
		num:num,
	}
}
func (m *AddDecorator)Calc() int{
	return m.Component.Calc() + m.num
}

// --------------------------------场景2-----------------------------------
// 回顾上面的案例, 发现如果想要拓展原有的功能的话需要嵌套对应的接口，接口：把实例抽象对来的对象
// 调用的话要通过接口调用原有的实例，模板方法是指定某个实例进行继承并且修改两个还是有些不太一样，一个面向类，一个面向实例，这里两种方法有点类似之前的原型设计和享元设计模式
// 原型设计模式面向的类，享元设计模式面向的是实例
// 关于设计模式重要的是去理解，并非去背案例，要从案例中理解原理和使用，并通过真实场景来运用，
// 需求：用户过生日买了一个”蛋糕“，
// 分析：蛋糕需要包装在包装盒子里，就变成了生日蛋糕,包装成儿童蛋糕就变成了儿童，包装成庆祝的就是变成了庆祝的
// DDD：Cake 蛋糕 属性 Name==>蛋糕[其他属性暂时忽略] BirthdayCake 生日蛋糕  。蛋糕模型：CakeModel,

// 蛋糕模型 提供给用户看选择
type CakeModel interface{
	Make() string 	// 分析该方法，返回一个string 也就是说明实力的对象拥有的属性
}
// 原始蛋糕没有做装饰的
type BaseCake struct{}
func (b *BaseCake)Make() string{
	return "蛋糕"
}

// 生日蛋糕
type BirthdayCake struct{
	CakeModel 		// 这里需要根据蛋糕模型来制作|毕竟设计师设计的
	Name 	string 	// 蛋糕名字
}
// 包装
func NewBirthdayCake(cakeModel CakeModel, name string) CakeModel{
	return &BirthdayCake{
		CakeModel:cakeModel,
		Name:name,
	}
}
// 制作
func (b *BirthdayCake) Make() string{
	return b.Name + b.CakeModel.Make()
}

// 下面可以拓展其他的蛋糕
// --------------------------------场景3-----------------------------------

// 需求：用户提现系统，提现金额限制，超过1万，将【”通知管理员/增加手续费/.....“】
// 分析：原始功能是提现， 额外的功能提现金额限制
// DDD: BalanceManagement 余额管理, Withdrawal 提现，Balancelimit 余额限制，Balance 原始余额功能

type BalanceManagement interface{
	Withdrawal() float64
}

type Balance struct{}

func (b *Balance)Withdrawal() float64{
	// 提现未限制前| 原始余额
	return 0.003 // 该数据可以配置
}

// 金额限制
type BalanceLimit struct{
	BalanceManagement
	amount 	float64
}

func NewBalanceLimit(balance BalanceManagement,amount float64) BalanceManagement{
	return &BalanceLimit{
		BalanceManagement:balance,
		amount:amount,
	}
}

func (b *BalanceLimit)Withdrawal() float64{
	// 增加汇率
	return b.amount - b.BalanceManagement.Withdrawal() * b.amount
}

// 金额通知业务
type BalanceNotice struct{
	BalanceManagement
}

func NewBalanceNotice(balance BalanceManagement) BalanceManagement{
	return &BalanceNotice{
		BalanceManagement:balance,
	}
}

func (b *BalanceNotice)Withdrawal() float64{
	fmt.Println("最终提现金额:",b.BalanceManagement.Withdrawal())
	return 0.00
}

// --------------------------------场景4-----------------------------------
// 结论关于装饰器设计模式，一般用户二次迭代或不修改原代码基础上来进行使用的
// 建议上述仅仅是练习 比如上面的金额通知这里使用装饰器并不好，当然如果将不同的方法才分使用建造者之后，如果对某个模块功能做增强时候可以考虑在使用装饰来进行
// 当然如果使用模板方法也是可以的，实现的方法有很多种，
// 在这里再简述一下建造者和外观设计的区分，『外观设计时面向接口，建造者是面向实例』，
// 还有 『原型是面向接口，享元是面向实例 』,『 模板是面向实例，装饰器是面向接口 』